Spark的优化

优化之前必须对要处理的数据量有一个充分的认识,比如:
1.哪些数据是需要的,哪些是不需要的,数据过滤可否提前;
2.多表链接的时候,先连接小表达到过滤较多数据的目的;
3.数据格式是什么样的;
4.数据是如何分布的,有多少分区,分区之间是否平衡;
5.执行计划是什么样的(通过Spark UI、Spark history Server、explain、toDebugString等)。
只有充分了解要处理的数据才能够做好优化。

1.RDD重新分区

针对大量小分区的RDD,使用RDD重分区函数coalesce将小分区合并成大分区;同样当分区数据量过大也可以使用重新分区,增加分区数量,提高并行计算能力。一般推荐每个分区12MB左右的数据,默认值为128MB。

具体措施:

1.1 读取数据时:

a.读取文件的时候设置分区(sparkContext.textFile("filePath",200));
b.使用Catalyst优化器管理DataFrame、DataSet的基础RDD的分区.

1.2 Transformation算子创建RDD的时:

Transformation算子创建的子RDD的分区数量依赖于父RDD.
a.通过修改spark.default.parappelism来制定默认值;
b.如果Transformation算子支持设置numPartitions,可以设置numPartitions,如reduceByKey();

备注:
coalesce默认不执行shuffle,因此只适用于分区个数由大变小的场景;
repartition也是调用了coalesce方法,只是shuffle为true,因此repartition执行了shuffle,适用于分区个数由小变大的场景。

2.并行度

2.1 通过配置和代码来设置task数量。task数量太多导致task的启动和切换开销;task数量太小,无法利用并行计算的能力;
2.2 使用hive sql时可指定并行度。

3.DAG优化

a.一个Stage尽量容纳更多的算子,减少Shuffle的发生;
b.将经常用到的数据缓存到内存,然后复用已经cache的数据。

4.倾斜问题

倾斜问题是指在个别分区上,执行时间过长,最长时长远大于平均时长。
数据倾斜(比如分区key或者分区函数设计的不好),task倾斜(数据倾斜导致任务倾斜),task执行速度倾斜(解决方式:去掉执行过长的task任务节点,重新分配调度)。
解决方式:
4.1.增大task数量,减少每个分区的数据量;
4.2.对特殊的key进行处理,如空值映射为特定key,分发到不同节点进行特殊处理;
4.3.将数据量大的表划分成多个小表,分成多个阶段进行;
4.4.拆分RDD:将倾斜数据与元数据分离,分成2个job进行计算。
4.5.优化聚集操作,避免使用shuffle。
4.6.使key随机化,如每个key后面加一个0-N的随机数,这样讲task拆分;
4.7.对4.6进行优化,只对数据量比较大的key进行key随机化操作,需要统计key的数据量,避免数据量小的没必要拆分key也没拆分;
4.8 针对一个RDD数据量小的情况,使用map join替换reduce join(不适合两个RDD都很大的场景);

5.JVM调优

5.1 设计好的数据结构,避免使用链式集合;
5.2 减少对象嵌套;
5.3 尽量使用数字ID或者枚举对象作为key,尽量避免使用字符串;
5.4 JVM的GC调优;
5.5 磁盘临时空间优化,当执行Shuffle或者内存不够缓存RDD时,RDD的数据会写到磁盘临时目录中;
5.5 JVM重复使用,因为一个task默认启动一个JVM,task结束之后,销毁JVM,JVM重复使用避免JVM的启动和销毁对资源的消耗。

6.网络传输优化

6.1 增大task的分发缓存大小,避免过大的task造成缓存区溢出;
6.2 小数据量表数据可以直接广播到各个Exector节点上,这样该Executor节点上的N个task共享广播数据,避免每个task都从Driver端获取该数据;
6.3 广播变量的经过是,将数据读取到driver端(类似于Collect函数),在分发到Executor上,不合理的广播变量会导致driver端的OOM。
6.4 Collect结果过大时要使用序列化,因为collect函数将每个分区变为数组,返回主节点,并将所有分区的数组合并成一个分组。

7.序列化和压缩

7.1 序列化存储RDD,官方推荐kyro序列化方式;
7.2 压缩存储RDD,spark支持LZF和snappy两种压缩方式;
7.2 Hadoop权衡行式存储和列式存储,行式存储查询过滤需要将所有数据遍历,列式存储在查询时,只需要读取需要的列块,因此查询性能更好,而且具有更好的压缩效率。
不同应用场景选择合适的存储格式:


不同应用场景选择合适的存储格式.png

8.方法的优化

8.1 尽量避免使用reduce方法,而使用reduceByKey

因为reduce是Action算子,是聚集函数,会导致结果汇聚到主节点;而reduceByKey是transformation算子,使计算变成分布式计算。

8.2 针对shuffle操作(如sortByKey,groupByKey,reduceByKey, join等),可以通过增加并行度

这样每个task处理的数据量就会相对减少,占用内存就会减少,避免OutOfMemory产生。

8.3 join和map join的选择

    map join通过Local Task将小表读入内存,生成HashTableFiles,并压缩上传至Distributed Cache中(map join 不适合两个都是大表的情况,否则会导致内存溢出)。
     在Map阶段,每个Mapper从Distributed Cache读取HashTableFiles到内存中,顺序扫描大表,在Map阶段实现join,将数据传递给下一个MapReduce任务,避免了shuffle,加快处理效率。

8.4 cache和persist选择

cache其实只是调用了persist()方法,默认缓存到内存中(persist(StorageLevel.MEMORY_ONLY));
persist支持多种缓存方式,具体如下:

object StorageLevel {
  val NONE = new StorageLevel(false, false, false, false)
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
...
}

class StorageLevel private (private var _useDisk: Boolean,private var_useMemory: Boolean, private var _useOffHeap: Boolean, private var _deserialized: Boolean, private var _replication: Int = 1)  extends Externalizable {
  ...
  def useDisk: Boolean = _useDisk
  def useMemory: Boolean = _useMemory
  def useOffHeap: Boolean = _useOffHeap
  def deserialized: Boolean = _deserialized
  def replication: Int = _replication
  ...
}

备注:
a._2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上,实现容错,避免因为一个RDD分区意外丢失导致所有数据重新计算;
b.
_SER 代表RDD的每个partition会被序列化成一个字节数组,节省内存。

9.文件大小

9.1 对于大文件,进行拆分,提高并行度

9.2 对于小文件进行合并
wholeTextFile方式将小文件都读入到一个文件中。

10.Catalyst优化器:

10.1 Catalyst优化器的目标:

a.减少executor之间的数据传输;
b.减少shuffle操作;
c.将尽可能多的操作纳入到同一个stage中;
d.运行时为整个stage生成代码。

10.2 Catalyst优化器优化动作:

a.filter前置;
b.select前置;
c.减少shuffle数据量,如聚合之前先内部聚合,join的时候选择最优方式和顺序;
d.常量折叠:编译的时候进行常量计算,将其折叠为文本;
e.其他:如Decimal计算转为long计算。

备注:Lambda影响Catalyst优化器优化

11.广播(broadcast)

广播(broadcast)可以将比较小的RDD或者DataFrame广播到各个机器节点上,在执行join的过程中可以避免shuffle过程,从而降低通信成本,提高执行效率。

在SparkSQL中,可以通过设置spark.sql.autoBroadcastJoinThreshold(默认值10MB),这样当DataFrame对应表的统计信息(有可能不是实际的表存储容量)小于spark.sql.autoBroadcastJoinThreshold,SparkSQL将join转为broadcast Join。可以适当提高spark.sql.autoBroadcastJoinThreshold的值,来提高广播的效率,但是由于广播数据需要经过Driver端,会给Driver造成存储压力,有可能会导致driver端的存储溢出,一般需要提高spark.driver.memory的值。

另外,当表的统计信息更新不及时的时候(如表的分区正在写入数据,spark判断是否需要广播该表,读取统计信息的时候,统计信息中的数据量还小于spark.sql.autoBroadcastJoinThreshold的值,当执行SparkSQL的时候,该表的数据量已经很大,SparkSQL仍然会broadcast该表),有可能会出现广播大表的风险,这种情况一般会出现driver端内存溢出。此时应当配置spark.sql.statistics.fallBackToHdfs=true,即Spark在执行的时候,直接读取HDFS文件的大小,而不是读取表的描述统计信息来进行广播判断的依据(曾经遇到过这样的线上案例,惨痛的教训)。

备注:可以通过hint设置,强制将指定表广播出去,不考虑spark.sql.autoBroadcastJoinThreshold的值。

12.资源分配:

a.yarn相关:
yarn.nodemanager.resource.memory-mb:控制每个节点可用内存大小;
yarn.nodemanager.resource.cpu-vcores:控制每个节点可用最大CPU数量;
b.Spark相关:
num-executors:executors的数量,可以设置Dynamic Allocation动态分配;

spark.dynamicAllocation.enabled // 是否启动Dynamic Allocation
spark.dynamicAllocation.minExecutors// 最小executor数量
spark.dynamicAllocation.maxExecutors// 最大executor数量
spark.dynamicAllocation.initialExecutors// 初始executor数量

executors-cores:每个executor的CPU vcore的数量,一般推荐<=5;
executors-memory:每个executor的内存大小,推荐64GB是上线,一般一个cpu配置4GB内存;

Spark内存管理.png

可以通过spark参数配置各区域大小,通过TaskMemoryManager查看各个部分使用量。

13.driver端:

1.有些数据是需要driver端传输数据的,如broadcast、shuffle时,数据是由driver端转发的,可以增加spark.driver.memory减少driver端的存储压力;
2.避免数据汇集到driver端的算子,如collect()(只适合少量数据)、toPandas()、reduce()(用treeReduce替换,可以在executor中先进行计算)、accumulators()。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容